﻿// Copyright 2010 ForgeCraft team
// Licensed under the Educational Community License, Version 2.0 
// http://www.osedu.org/licenses/ECL-2.0

using System;
using System.Collections.Generic;
using System.IO;

namespace ForgeCraft {
    public class Group
    {
        public string name;
        public string trueName;
        public string color;
        public Permission permission;

        public int maxBlocks,
                   idleKickTimer,
                   drawLimit,
                   antiGriefBlocks = 35,
                   antiGriefSeconds = 5;

        public bool reservedSlot, drawLimitLocked;
        public int idleKickAfter;

        public bool canDelete;
        public bool canBuild;

        public CommandList commands;
        public string fileName;
        public PlayerList playerList;

        //Is this the right place?
        

        public Group()
        {
            permission = Permission.Null;
        }

        public Group(Permission Perm, int maxB, string fullName, char newColor, string file, bool build, bool opSlot, int IdleK, int draw)
        {
            permission = Perm;
            maxBlocks = maxB;
            trueName = fullName;
            name = trueName.ToLower();
            color = "§" + newColor;
            fileName = file;
            canDelete = build;
            canBuild = build;
            reservedSlot = opSlot;
            idleKickAfter = IdleK;
            drawLimit = draw;

            if (drawLimit > 10) drawLimitLocked = false;
            else drawLimitLocked = true;

            if (name != "nobody")
                playerList = PlayerList.Load(fileName, this);
            else
                playerList = new PlayerList();
        }

        public void fillCommands()
        {
            CommandList _commands = new CommandList();
            GrpCommands.AddCommands(out _commands, permission);
            commands = _commands;
        }

        public bool CanExecute(Command cmd) { return commands.Contains(cmd); }

        public static List<Group> GroupList = new List<Group>();
        public static Group standard;
        public static void InitAll()
        {
            GroupList = new List<Group>();

            if (File.Exists("properties/ranks.properties"))
            {
                string[] lines = File.ReadAllLines("properties/ranks.properties");

                Group thisGroup = new Group();
                int gots = 0;

                foreach (string s in lines)
                {
                    try
                    {
                        if (s != "" && s[0] != '#')
                        {
                            if (s.Split('=').Length == 2)
                            {
                                string property = s.Split('=')[0].Trim();
                                string value = s.Split('=')[1].Trim();

                                if (thisGroup.name == "" && property.ToLower() != "rankname")
                                {
                                    Logger.Log("Hitting an error at " + s + " of ranks.properties", LogType.Error);
                                }
                                else
                                {
                                    switch (property.ToLower())
                                    {
                                        case "rankname":
                                            gots = 0;
                                            thisGroup = new Group();

                                            if (value.ToLower() == "developers" || value.ToLower() == "devs")
                                                Logger.Log("You are not a developer. Stop pretending you are.", LogType.Error);
                                            else if (GroupList.Find(grp => grp.name == value.ToLower()) == null)
                                                thisGroup.trueName = value;
                                            else
                                                Logger.Log("Cannot add the rank " + value + " twice", LogType.Error);
                                            break;
                                        case "permission":
                                            int foundpermission;

                                            try
                                            {
                                                foundpermission = int.Parse(value);
                                            }
                                            catch { Logger.Log("Invalid permission on " + s, LogType.Error); break; }

                                            if (thisGroup.permission != Permission.Null)
                                            {
                                                Logger.Log("Setting permission again on " + s, LogType.Error);
                                                gots--;
                                            }

                                            bool allowed = true;
                                            if (GroupList.Find(grp => grp.permission == (Permission)foundpermission) != null)
                                                allowed = false;

                                            if (foundpermission > 119 || foundpermission < -50)
                                            {
                                                Logger.Log("permission must be between -50 and 119 for ranks", LogType.Warning);
                                                break;
                                            }

                                            if (allowed)
                                            {
                                                gots++;
                                                thisGroup.permission = (Permission)foundpermission;
                                            }
                                            else
                                            {
                                                Logger.Log("Cannot have 2 ranks set at permission level " + value, LogType.Warning);
                                            }
                                            break;
                                        case "limit":
                                            int foundLimit;

                                            try
                                            {
                                                foundLimit = int.Parse(value);
                                            }
                                            catch { Logger.Log("Invalid limit on " + s, LogType.Warning); break; }

                                            gots++;
                                            thisGroup.maxBlocks = foundLimit;
                                            break;
                                        case "draw":
                                            int founddraw;

                                            try
                                            {
                                                founddraw = int.Parse(value);
                                            }
                                            catch { Logger.Log("Invalid distance on " + s, LogType.Warning); break; }

                                            gots++;
                                            thisGroup.drawLimit = founddraw;
                                            break;
                                        case "drawLock":
                                            bool founddrawlock;

                                            try
                                            {
                                                founddrawlock = bool.Parse(value);
                                            }
                                            catch { Logger.Log("Invalid bool on " + s, LogType.Warning); break; }

                                            thisGroup.drawLimitLocked = founddrawlock;
                                            break;
                                        case "canBuild":
                                            bool foundBuild;

                                            try
                                            {
                                                foundBuild = bool.Parse(value);
                                            }
                                            catch { Logger.Log("Invalid limit on " + s, LogType.Warning); break; }

                                            gots++;
                                            thisGroup.canBuild = foundBuild;
                                            break;

                                        case "canDelete":
                                            bool foundDelete;

                                            try
                                            {
                                                foundDelete = bool.Parse(value);
                                            }
                                            catch { Logger.Log("Invalid limit on " + s, LogType.Warning); break; }

                                            gots++;
                                            thisGroup.canDelete = foundDelete;
                                            break;
                                        case "reserverdSlot":
                                            bool foundReserved;

                                            try
                                            {
                                                foundReserved = bool.Parse(value);
                                            }
                                            catch { Logger.Log("Invalid limit on " + s, LogType.Warning); break; }

                                            gots++;
                                            thisGroup.reservedSlot = foundReserved;
                                            break;
                                        case "idleKickAfter":
                                            int foundidleKickAfter;

                                            try
                                            {
                                                foundidleKickAfter = int.Parse(value);
                                            }
                                            catch { Logger.Log("Invalid limit on " + s, LogType.Warning); break; }

                                            gots++;
                                            thisGroup.idleKickAfter = foundidleKickAfter;
                                            break;
                                        case "color":
                                            char foundChar;

                                            try
                                            {
                                                foundChar = char.Parse(value);
                                            }
                                            catch { Logger.Log("Incorrect color on " + s, LogType.Warning); break; }

                                            if ((foundChar >= '0' && foundChar <= '9') || (foundChar >= 'a' && foundChar <= 'f'))
                                            {
                                                gots++;
                                                thisGroup.color = foundChar.ToString();
                                            }
                                            else
                                            {
                                                Logger.Log("Invalid color code at " + s, LogType.Warning);
                                            }
                                            break;
                                        case "filename":
                                            if (value.Contains("\\") || value.Contains("/"))
                                            {
                                                Logger.Log("Invalid filename on " + s, LogType.Error);
                                                break;
                                            }

                                            gots++;
                                            thisGroup.fileName = value;
                                            break;
                                    }

                                    if (gots >= 6)
                                    {
                                        GroupList.Add(new Group(thisGroup.permission, thisGroup.maxBlocks, thisGroup.trueName, thisGroup.color[0], thisGroup.fileName, thisGroup.canBuild, thisGroup.reservedSlot, thisGroup.idleKickAfter, thisGroup.drawLimit));
                                    }
                                }
                            }
                            else
                            {
                                Logger.Log("In ranks.properties, the line " + s + " is wrongly formatted", LogType.Warning);
                            }
                        }
                    }
                    catch { }
                }
            }

            if (GroupList.Find(grp => grp.permission == Permission.Banned) == null) GroupList.Add(new Group(Permission.Banned, 1, "Banned", '8', "banned.txt", false, false, 10, 10));
            if (GroupList.Find(grp => grp.permission == Permission.Guest) == null) GroupList.Add(new Group(Permission.Guest, 1, "Guest", '7', "guest.txt", true, false, 10, 10));
            if (GroupList.Find(grp => grp.permission == Permission.Builder) == null) GroupList.Add(new Group(Permission.Builder, 400, "Builder", '2', "builders.txt", true, false, 10, 16));
            if (GroupList.Find(grp => grp.permission == Permission.AdvBuilder) == null) GroupList.Add(new Group(Permission.AdvBuilder, 1200, "AdvBuilder", '3', "advbuilders.txt", true, false, 10, 16));
            if (GroupList.Find(grp => grp.permission == Permission.Operator) == null) GroupList.Add(new Group(Permission.Operator, 2500, "Operator", 'c', "operators.txt", true, true, 0, 16));
            if (GroupList.Find(grp => grp.permission == Permission.Admin) == null) GroupList.Add(new Group(Permission.Admin, 65536, "SuperOP", 'e', "uberOps.txt", true, true, 0, 30));
            GroupList.Add(new Group(Permission.Nobody, 65536, "Nobody", '0', "nobody.txt", false, false, 0, 0));

            bool swap = true; Group storedGroup;
            while (swap)
            {
                swap = false;
                for (int i = 0; i < GroupList.Count - 1; i++)
                    if (GroupList[i].permission > GroupList[i + 1].permission)
                    {
                        swap = true;
                        storedGroup = GroupList[i];
                        GroupList[i] = GroupList[i + 1];
                        GroupList[i + 1] = storedGroup;
                    }
            }

            //if (Group.Find(Server.defaultRank) != null) standard = Group.Find(Server.defaultRank);
            //else standard = Group.findPerm(Permission.Guest);
            standard = Group.findPerm(Permission.Guest);

            /*foreach (Player pl in Server.playerList)
            {
                pl.group = GroupList.Find(g => g.name == pl.group.name);
            }*/

            saveGroups(GroupList);
        }
        public static void saveGroups(List<Group> givenList)
        {
            if (!Directory.Exists("properties")) Directory.CreateDirectory("properties");

            StreamWriter SW = new StreamWriter(File.Create("properties/ranks.properties"));
            SW.WriteLine("#RankName = string");
            SW.WriteLine("#     The name of the rank, use capitalization.");
            SW.WriteLine("#");
            SW.WriteLine("#permission = num");
            SW.WriteLine("#     The \"permission\" of the rank. It's a number.");
            SW.WriteLine("#		There are pre-defined permissions already set. (for the old ranks)");
            SW.WriteLine("#		Banned = -20, Guest = 0, Builder = 30, AdvBuilder = 50, Operator = 80");
            SW.WriteLine("#		SuperOP = 100, Nobody = 120");
            SW.WriteLine("#		Must be greater than -50 and less than 120");
            SW.WriteLine("#		The higher the number, the more commands do (such as undo allowing more seconds)");
            SW.WriteLine("#Limit = num");
            SW.WriteLine("#     The command limit for the rank (can be changed in-game with /limit)");
            SW.WriteLine("#		Must be greater than 0 and less than 10000000");
            SW.WriteLine("#Color = char");
            SW.WriteLine("#     A single letter or number denoting the color of the rank");
            SW.WriteLine("#	    Possibilities:");
            SW.WriteLine("#		    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f");
            SW.WriteLine("#FileName = string.txt");
            SW.WriteLine("#     The file which players of this rank will be stored in");
            SW.WriteLine("#		It doesn't need to be a .txt file, but you may as well");
            SW.WriteLine("#		Generally a good idea to just use the same file name as the rank name");
            SW.WriteLine();
            SW.WriteLine();

            foreach (Group grp in givenList)
            {
                if (grp.name != "nobody")
                {
                    SW.WriteLine("RankName = " + grp.trueName);
                    SW.WriteLine("permission = " + (int)grp.permission);
                    SW.WriteLine("Limit = " + grp.maxBlocks);
                    SW.WriteLine("Draw = " + grp.drawLimit);
                    SW.WriteLine("DrawLock = " + grp.drawLimitLocked);
                    SW.WriteLine("CanBuild = " + grp.canBuild);
                    SW.WriteLine("CanDelete = " + grp.canDelete);
                    SW.WriteLine("ReservedSlot = " + grp.reservedSlot);
                    SW.WriteLine("IdleKickAfter = " + grp.idleKickAfter);
                    SW.WriteLine("Color = " + grp.color[1]);
                    SW.WriteLine("FileName = " + grp.fileName);
                    SW.WriteLine();
                }
            }

            SW.Flush();
            SW.Close();
        }

        public static bool Exists(string name)
        {
            name = name.ToLower();
            foreach (Group gr in GroupList)
            {
                if (gr.name == name.ToLower()) { return true; }
            } return false;
        }
        public static Group Find(string name)
        {
            name = name.ToLower();

            if (name == "adv") name = "advbuilder";
            if (name == "op") name = "operator";
            if (name == "super" || name == "admin") name = "superop";
            if (name == "noone") name = "nobody";

            foreach (Group gr in GroupList)
            {
                if (gr.name == name.ToLower()) { return gr; }
            } return null;
        }
        public static Group findPerm(Permission Perm)
        {
            foreach (Group grp in GroupList)
            {
                if (grp.permission == Perm) return grp;
            }
            return null;
        }

        public static string findPlayer(string playerName)
        {
            foreach (Group grp in Group.GroupList)
            {
                if (grp.playerList.Contains(playerName)) return grp.name;
            }
            return Group.standard.name;
        }

        public static Group findPlayerGroup(string playerName)
        {
            foreach (Group grp in Group.GroupList)
            {
                if (grp.playerList.Contains(playerName)) return grp;
            }
            return Group.standard;
        }

        public static string concatList(bool includeColor = true, bool skipExtra = false, bool permissions = false)
        {
            string returnString = "";
            foreach (Group grp in Group.GroupList)
            {
                if (!skipExtra || (grp.permission > Permission.Guest && grp.permission < Permission.Nobody))
                    if (includeColor)
                    {
                        returnString += ", " + grp.color + grp.name + Color.Announcement;
                    }
                    else if (permissions)
                    {
                        returnString += ", " + ((int)grp.permission).ToString();
                    }
                    else
                        returnString += ", " + grp.name;
            }

            if (includeColor) returnString = returnString.Remove(returnString.Length - 2);

            return returnString.Remove(0, 2);
        }
    }

    public class GrpCommands
    {
        public class rankAllowance
        {
            public string commandName;
            public Permission baseRank;
            public List<Permission> disallow = new List<Permission>();
            public List<Permission> allow = new List<Permission>();
        }
        public static List<rankAllowance> allowedCommands;
        public static List<string> foundCommands = new List<string>();

        public static Permission defaultRanks(string command)
        {
            Command cmd = Command.all.Find(command);

            if (cmd != null) return cmd.defaultRank;
            else return Permission.Null;
        }

        public static void fillRanks()
        {
            foundCommands = Command.all.commandNames();
            allowedCommands = new List<rankAllowance>();

            rankAllowance allowVar;

            foreach (Command cmd in Command.all.All())
            {
                allowVar = new rankAllowance();
                allowVar.commandName = cmd.name;
                allowVar.baseRank = cmd.defaultRank;
                allowedCommands.Add(allowVar);
            }

            if (File.Exists("properties/command.properties"))
            {
                string[] lines = File.ReadAllLines("properties/command.properties");

                string[] colon = new string[] { " : " };
                foreach (string line in lines)
                {
                    allowVar = new rankAllowance();
                    if (line != "" && line[0] != '#')
                    {
                        //Name : Lowest : Disallow : Allow
                        string[] command = line.Split(colon, StringSplitOptions.None);

                        if (!foundCommands.Contains(command[0]))
                        {
                            Logger.Log("Incorrect command name: " + command[0], LogType.Warning);
                            continue;
                        }
                        allowVar.commandName = command[0];

                        string[] disallow = new string[0];
                        if (command[2] != "")
                            disallow = command[2].Split(',');
                        string[] allow = new string[0];
                        if (command[3] != "")
                            allow = command[3].Split(',');

                        try
                        {
                            allowVar.baseRank = (Permission)int.Parse(command[1]);
                            foreach (string s in disallow) { allowVar.disallow.Add((Permission)int.Parse(s)); }
                            foreach (string s in allow) { allowVar.allow.Add((Permission)int.Parse(s)); }
                        }
                        catch
                        {
                            Logger.Log("Hit an error on the command " + line, LogType.Warning);
                            continue;
                        }

                        int current = 0;
                        foreach (rankAllowance aV in allowedCommands)
                        {
                            if (command[0] == aV.commandName)
                            {
                                allowedCommands[current] = allowVar;
                                break;
                            }
                            current++;
                        }
                    }
                }
                Save(allowedCommands);
            }
            else Save(allowedCommands);

            foreach (Group grp in Group.GroupList)
            {
                grp.fillCommands();
            }
        }

        public static void Save(List<rankAllowance> givenList)
        {
            try
            {
                StreamWriter w = new StreamWriter(File.Create("properties/command.properties"));
                w.WriteLine("#   This file contains a reference to every command found in the server software");
                w.WriteLine("#   Use this file to specify which ranks get which commands");
                w.WriteLine("#   Current ranks: " + Group.concatList(false, false, true));
                w.WriteLine("#   Disallow and allow can be left empty, just make sure there's 2 spaces between the colons");
                w.WriteLine("#   This works entirely on permission values, not names. Do not enter a rank name. Use it's permission value");
                w.WriteLine("#   CommandName : LowestRank : Disallow : Allow");
                w.WriteLine("#   sethome : 60 : 80,67 : 40,41,55");
                w.WriteLine("");
                foreach (rankAllowance aV in givenList)
                {
                    w.WriteLine(aV.commandName + " : " + (int)aV.baseRank + " : " + getInts(aV.disallow) + " : " + getInts(aV.allow));
                }
                w.Flush();
                w.Close();
            }
            catch
            {
                Logger.Log("SAVE FAILED! command.properties", LogType.Error);
            }
        }
        public static string getInts(List<Permission> givenList)
        {
            string returnString = ""; bool foundOne = false;
            foreach (Permission Perm in givenList)
            {
                foundOne = true;
                returnString += "," + (int)Perm;
            }
            if (foundOne) returnString = returnString.Remove(0, 1);
            return returnString;
        }
        public static void AddCommands(out CommandList commands, Permission perm)
        {
            commands = new CommandList();

            foreach (rankAllowance aV in allowedCommands)
                if ((aV.baseRank <= perm && !aV.disallow.Contains(perm)) || aV.allow.Contains(perm)) commands.Add(Command.all.Find(aV.commandName));
        }
    }
}